package com.a918.consensus;//import java.util.ArrayList;
//import java.util.Collections;
//import java.util.DoubleSummaryStatistics;
//import java.util.List;
//import java.util.Optional;
//import java.util.Random;
//import java.util.stream.Collectors;
//
//public class Main {
//  private static final int RANDOM_SEED = 12345;
//  private static final double TIME_LIMIT = 4;
//  private static final int SAMPLES = 1000;
//
//  public static void main(String[] args) {
//    // Print the first row which contains column names.
//    System.out.println("initial_timeout, tendermint, algorand, this_work");
//
//    double tendermintBestLatency = Double.MAX_VALUE, tendermintBestTimeout = 0;
//    double algorandBestLatency = Double.MAX_VALUE, algorandBestTimeout = 0;
//    double mirBestLatency = Double.MAX_VALUE, mirBestTimeout = 0;
//
//    for (double initalTimeout = 0.12; initalTimeout <= 0.4; initalTimeout += 0.01) {
//      DoubleSummaryStatistics tendermintOverallStats = new DoubleSummaryStatistics(),
//          algorandOverallStats = new DoubleSummaryStatistics(),
//          mirOverallStats = new DoubleSummaryStatistics();
//      for (int i = 0; i < SAMPLES; ++i) {
//        Optional<DoubleSummaryStatistics> tendermintStats =
//            runTendermint(initalTimeout, 90, 10);
//        Optional<DoubleSummaryStatistics> algorandStats =
//            runAlgorand(initalTimeout, 90, 10);
//        Optional<DoubleSummaryStatistics> mirStats =
//            runMir(initalTimeout, 90, 10);
//
//        tendermintStats.ifPresent(tendermintOverallStats::combine);
//        algorandStats.ifPresent(algorandOverallStats::combine);
//        mirStats.ifPresent(mirOverallStats::combine);
//      }
//
//      if (tendermintOverallStats.getCount() > 0 &&
//          tendermintOverallStats.getAverage() < tendermintBestLatency) {
//        tendermintBestLatency = tendermintOverallStats.getAverage();
//        tendermintBestTimeout = initalTimeout;
//      }
//      if (algorandOverallStats.getCount() > 0 &&
//          algorandOverallStats.getAverage() < algorandBestLatency) {
//        algorandBestLatency = algorandOverallStats.getAverage();
//        algorandBestTimeout = initalTimeout;
//      }
//      if (mirOverallStats.getCount() > 0 &&
//          mirOverallStats.getAverage() < mirBestLatency) {
//        mirBestLatency = mirOverallStats.getAverage();
//        mirBestTimeout = initalTimeout;
//      }
//
//      System.out.printf("%.2f, %s, %s, %s\n",
//          initalTimeout,
//          tendermintOverallStats.getCount() > 0 ? tendermintOverallStats.getAverage() : "",
//          algorandOverallStats.getCount() > 0 ? algorandOverallStats.getAverage() : "",
//          mirOverallStats.getCount() > 0 ? mirOverallStats.getAverage() : "");
//    }
//
//    System.out.println();
//    System.out.printf("Tendermint best with timeout %.2f: %.4f\n",
//        tendermintBestTimeout, tendermintBestLatency);
//    System.out.printf("Algorand best with timeout %.2f: %.4f\n",
//        algorandBestTimeout, algorandBestLatency);
//    System.out.printf("Mir best with timeout %.2f: %.4f\n",
//        mirBestTimeout, mirBestLatency);
//    double secondBestLatency = Math.min(tendermintBestLatency, algorandBestLatency);
//    System.out.printf("Mir speedup: %.4f\n",
//        (secondBestLatency - mirBestLatency) / secondBestLatency);
//  }
//
//  private static Optional<DoubleSummaryStatistics> runTendermint(
//      double initialTimeout, int correctNodeCount, int failedNodeCount) {
//    Random random = new Random();
//    List<Node> nodes = new ArrayList<>();
//    for (int i = 0; i < correctNodeCount; ++i) {
//      EarthPosition position = EarthPosition.randomPosition(random);
//      nodes.add(new CorrectTendermintNode(position, initialTimeout));
//    }
//    for (int i = 0; i < failedNodeCount; ++i) {
//      EarthPosition position = EarthPosition.randomPosition(random);
//      nodes.add(new FailedNode(position));
//    }
//    Collections.shuffle(nodes, random);
//
//    Network network = new FullyConnectedNetwork(nodes, random);
//    Simulation simulation = new Simulation(network);
//    if (!simulation.run(TIME_LIMIT)) {
//      return Optional.empty();
//    }
//
//    List<Node> correctNodes = nodes.stream()
//        .filter(n -> n instanceof CorrectTendermintNode)
//        .collect(Collectors.toList());
//    if (!correctNodes.stream().allMatch(Node::hasTerminated)) {
//      System.out.println("WARNING: Not all Tendermint nodes terminated.");
//      return Optional.empty();
//    }
//
//    return Optional.of(correctNodes.stream()
//        .mapToDouble(Node::getTerminationTime)
//        .summaryStatistics());
//  }
//
//  private static Optional<DoubleSummaryStatistics> runAlgorand(
//      double initialTimeout, int correctNodeCount, int failedNodeCout) {
//    Random random = new Random();
//    List<Node> nodes = new ArrayList<>();
//    for (int i = 0; i < correctNodeCount; ++i) {
//      EarthPosition position = EarthPosition.randomPosition(random);
//      nodes.add(new CorrectAlgorandNode(position, initialTimeout));
//    }
//    for (int i = 0; i < failedNodeCout; ++i) {
//      EarthPosition position = EarthPosition.randomPosition(random);
//      nodes.add(new FailedNode(position));
//    }
//    Collections.shuffle(nodes, random);
//
//    Network network = new FullyConnectedNetwork(nodes, random);
//    Simulation simulation = new Simulation(network);
//    if (!simulation.run(TIME_LIMIT)) {
//      return Optional.empty();
//    }
//
//    List<Node> correctNodes = nodes.stream()
//        .filter(n -> n instanceof CorrectAlgorandNode)
//        .collect(Collectors.toList());
//    if (!correctNodes.stream().allMatch(Node::hasTerminated)) {
//      System.out.println("WARNING: Not all Algorand nodes terminated.");
//      return Optional.empty();
//    }
//
//    //System.out.println("Algorand times: " + correctNodes.stream().mapToDouble(Node::getTerminationTime).sorted().boxed().collect(Collectors.toList()));
//    return Optional.of(nodes.stream()
//        .mapToDouble(Node::getTerminationTime)
//        .summaryStatistics());
//  }
//
//  private static Optional<DoubleSummaryStatistics> runMir(
//      double initialTimeout, int correctNodeCount, int failedNodeCount) {
//    Random random = new Random();
//    List<Node> nodes = new ArrayList<>();
//    for (int i = 0; i < correctNodeCount; ++i) {
//      EarthPosition position = EarthPosition.randomPosition(random);
//      nodes.add(new CorrectMirNode(position, initialTimeout));
//    }
//    for (int i = 0; i < failedNodeCount; ++i) {
//      EarthPosition position = EarthPosition.randomPosition(random);
//      nodes.add(new FailedNode(position));
//    }
//    Collections.shuffle(nodes, random);
//
//    Network network = new FullyConnectedNetwork(nodes, random);
//    Simulation simulation = new Simulation(network);
//    if (!simulation.run(TIME_LIMIT)) {
//      return Optional.empty();
//    }
//
//    List<Node> correctNodes = nodes.stream()
//        .filter(n -> n instanceof CorrectMirNode)
//        .collect(Collectors.toList());
//    if (!correctNodes.stream().allMatch(Node::hasTerminated)) {
//      System.out.println("WARNING: Not all Mir nodes terminated.");
//      return Optional.empty();
//    }
//
//    //System.out.println("Mir times: " + correctNodes.stream().mapToDouble(Node::getTerminationTime).sorted().boxed().collect(Collectors.toList()));
//    return Optional.of(nodes.stream()
//        .mapToDouble(Node::getTerminationTime)
//        .summaryStatistics());
//  }
//
//  private static String statisticsToCompactString(DoubleSummaryStatistics statistics) {
//    return String.format("min=%.2f, max=%.2f, average=%.2f",
//        statistics.getMin(), statistics.getMax(), statistics.getAverage());
//  }
//}
import com.a918.entity.BlockInfo;

import java.util.*;
import java.util.stream.Collectors;

// Main副本
public class ConsensusAlgorithmOperation {
    private static final int RANDOM_SEED = 12345;
    private static final double TIME_LIMIT = 4;
    private static final int SAMPLES = 1000;
    private static final double reward = 0.5;   //出块奖励
    private static final double beta = 0.8;           //普通节点奖励占比
    private static final int round=6;
    private static final int correctNodeCnt=90; //正常节点
    private static final int failNodeCnt=10;  //恶意节点
    private static final int token=20;
    private static final double credit=0.5;
    private static final int delegetNum=21;
    private static final double initialTimeout = 0.1;


    public static void main(String[] args) {
        Map<String,Object> result = ConsensusAlgorithmOperation.getDPOSPTBlockInfo(5,"DPOSPT",initialTimeout,correctNodeCnt,failNodeCnt,token,credit,round,delegetNum,TIME_LIMIT,reward,beta);

        Map<String,Double> statistic = new HashMap<>();
        statistic.put("FailRate", (Double) result.get("FailRate"));
        statistic.put("GiniIndex", (Double) result.get("GiniIndex"));
        statistic.put("AvgTime", (Double) result.get("AvgTime"));
        System.out.println("统计信息:"+statistic);
        Map<Integer,Map<String,Object>> blockInfoMap = (Map<Integer, Map<String, Object>>) result.get("blockInfo");
        List<BlockInfo> list = new ArrayList<>();
        for (int r = 1;r <= round;r++) {
            Map<String,Object> voteInfo = blockInfoMap.get(r);
            List<Node> selectedDelegates = (List<Node>) voteInfo.get("selectedDelegates");
            System.out.println("见证人节点数量:"+selectedDelegates.size());
            List<Map<String,Object>> vlist = (List<Map<String, Object>>) voteInfo.get("blockInfo");
            for (Map<String, Object> map : vlist) {
                BlockInfo blockInfo = new BlockInfo();
                Block block = (Block) map.get("区块");
                Node creator = (Node) map.get("生产节点");
                Node failNode = (Node) map.get("作恶节点");
                if (failNode==null) {
                    // 出块成功
                    blockInfo.setBlockIndex(block.getIndex());
                    blockInfo.setPreviousHash(block.getPreviousHash());
                    blockInfo.setTimestamp(block.getTimestamp());
                    blockInfo.setNodeIndex(creator.toString());
                    blockInfo.setToken(creator.getToken());
                    blockInfo.setToken(creator.getCredit());
                    blockInfo.setStatus(1);
                } else {
                    // 节点作恶
                    blockInfo.setNodeIndex(failNode.toString());
                    blockInfo.setToken(failNode.getToken());
                    blockInfo.setToken(failNode.getCredit());
                    blockInfo.setStatus(0);
                }
                System.out.println(blockInfo);
                list.add(blockInfo);
            }
        }

    }
    // 接口示例：
    /*
     输入参数可以不一样，可以有任意多个。
    */
    public static Map<Double,Double> GetVerifyTimeDelay1(double initialTimeout, int correctNodeCount, int failedNodeCout, double token, double credit, int round, int delegateNum){
        Map<Double,Double> verifyTimeDelay = new HashMap<>();//数据类型不一定是Double, Integer等数值类型也可以...
    /*
    运行代码,可能运行多次，得到多组Xi:Yi。每得到一组Xi:Yi，就verifyTimeDelay.put(Xi,Yi)
     */
        return verifyTimeDelay;// {X1:Y1, X2:Y2, ... }
    }

    public static Map<String,Map<Double,Double>> GetVerifyTimeDelay2(double initialTimeout, int correctNodeCount, int failedNodeCout, double token, double credit, int round, int delegateNum){
        Map<Double,Double> pbftVerifyTimeDelay = new HashMap<>();//数据类型不一定是Double, Integer等数值类型也可以...
        Map<Double,Double> dposptVerifyTimeDelay = new HashMap<>();//数据类型不一定是Double, Integer等数值类型也可以...
    /*
    运行代码,可能运行多次，得到多组Xi:Yi。每得到一组Xi:Yi，就verifyTimeDelay.put(Xi,Yi)
     */
        Map<String,Map<Double,Double>> result = new HashMap<>();
        result.put("pbftVerifyTimeDelay",pbftVerifyTimeDelay);
        result.put("dposptVerifyTimeDelay",dposptVerifyTimeDelay);
        return result;// {"pbftVerifyTimeDelay":{X1:Y1, X2:Y2, ... }, "dposptVerifyTimeDelay":{X1:Y1, X2:Y2, ... }, ... }
    }

    public static Map<String,Object> getDPOSPTBlockInfo(int n,String type, double initialTimeout, int correctNodeCount, int failedNodeCout, double token, double credit, int round, int delegateNum, double TIME_LIMIT, double reward, double beta){
        // 返回内容： 区块信息，节点信息，统计信息(验证时延等)
        Map<String,Object> map = new HashMap<>();
        // 1. 区块信息
        // 2. 节点信息

        double sumFR=0;
        double sumGini=0;
        double sumAvgTime=0;
        List<Node> nodes = new ArrayList<>();
        Map<Integer,Map<String,Object>> result = new HashMap<>();
        for (int i=0;i<n;i++) {
            result = runDPOSPTGetInfo(initialTimeout, correctNodeCount, failedNodeCout, token, credit, round, delegateNum,TIME_LIMIT, reward, beta);
            // 1. 全部节点信息
            nodes = (List<Node>) result.get(0).get("nodeInfo");

            // 3. 统计信息(验证时延等)
            List<Double> DPOSPTOverallStatsList = new ArrayList<>();
            DPOSPTOverallStatsList = (List<Double>) result.get(0).get("statsListInfo");
            sumFR+=DPOSPTOverallStatsList.get(0);
            sumGini+=DPOSPTOverallStatsList.get(1);
            sumAvgTime+=DPOSPTOverallStatsList.get(2);
        }
        map.put("FailRate",sumFR/n);
        map.put("GiniIndex",sumGini/n);
        map.put("AvgTime",sumAvgTime/n);
        map.put("nodeInfo",nodes);
        map.put("blockInfo",result);

        return map;
    }


    private static List<Double> runDPOS(
            double initialTimeout, int correctNodeCount, int failedNodeCout, double token, double credit, int round, int delegateNum, double reward) {
        Random random = new Random();
        List<Node> nodes = new ArrayList<>();
        List<Node> delegates;
        List<Double> DPOSTokenOverallStats;
        List<Node> lastDelegates = new ArrayList<>();
        List<Double> rechooseRateList = new ArrayList<>(),DPOSOverallStatsList = new ArrayList<>(),failRateList = new ArrayList<>();
        int failCount=0;
        for (int i = 0; i < correctNodeCount; ++i) {
            EarthPosition position = EarthPosition.randomPosition(random);
            nodes.add(new DPoSNode(position, initialTimeout, credit, token));
        }
        for (int i = 0; i < failedNodeCout; ++i) {
            EarthPosition position = EarthPosition.randomPosition(random);
            nodes.add(new FailedNode(position,credit,token));
        }
        Collections.shuffle(nodes, random);

        for(int r = 0;r < round;r++){                                            //模拟n轮投票
            HashMap<Node,Double> voteTimeMap = new HashMap<>();

            HashMap<Node, List<Node>> votesMap = getVotes(nodes,"DPoS");    //模拟投票
//      delegates = getDelegatesByVotes(votesMap, delegateNum);      //选出见证人
            delegates = getDelegatesByVotes(votesMap, delegateNum*2);      //选出共识见证人和备选见证人

            delegates = delegates.subList(0, delegates.size() / 2);  //选出共识见证人节点
            //System.out.println("共识见证人节点：");
            for (Node delegate : delegates) {
                //System.out.println(delegate.toString());
            }


            failRateList.add(Double.valueOf(delegates.stream().filter(n->n instanceof FailedNode).count())/delegates.size());
            if(!lastDelegates.isEmpty()){
                List<Node> finalLastDelegates = lastDelegates;
                double lastFailNodes = finalLastDelegates.stream().filter(n->n instanceof FailedNode).count();
                double rechooseRate = lastFailNodes == 0?0.0:delegates.stream().filter(n->n instanceof FailedNode && finalLastDelegates.contains(n)).count()/lastFailNodes;
                rechooseRateList.add(rechooseRate);
                lastDelegates = new ArrayList<>(delegates);
            }else{
                lastDelegates = new ArrayList<>(delegates);
            }

            Collections.shuffle(delegates, random);                                 //随机打乱

            Network network = new FullyConnectedNetwork(delegates, random);
            for (int i = 0; i < delegateNum; i++) {                                  //这里每个共识见证人出块一次
                network.setCreatorId(i);
                Node creator = network.getCreator();
                if(creator instanceof DPoSNode) {
                    creator.setToken(creator.getToken() + reward);//更新生产节点奖励
                    //System.out.println("更新生产节点奖励");
                    //System.out.println(creator.getToken() + reward);
                    //System.out.println(creator.toString());
                } else{
                    failCount++;
                }
            }
        }
        DPOSTokenOverallStats = nodes.stream().filter(node -> node.getToken()>=0)
                .map(Node::getToken)
                .collect(Collectors.toList());//统计金额
//    DPOSOverallStatsList.add(round>1?rechooseRateList.stream().mapToDouble(n->n).average().getAsDouble():0.);
        DPOSOverallStatsList.add(failRateList.stream().mapToDouble(n->n).average().getAsDouble());
        DPOSOverallStatsList.add(gini(DPOSTokenOverallStats));
//    System.out.println(round*delegateNum-failCount);
//    nodes.stream().filter(node -> node instanceof DPoSNode).forEach(x->System.out.println(x.getCredit()+":"+x.getToken()));
//    nodes.stream().filter(node -> node instanceof FailedNode).forEach(x->System.out.println(x.getCredit()+":"+x.getToken()));
//    rechooseRateList.stream().forEach(x->System.out.println(x));
        return DPOSOverallStatsList;
    }

    private static List<Double> runDPOSPT(
            double initialTimeout, int correctNodeCount, int failedNodeCout, double token, double credit, int round, int delegateNum, double TIME_LIMIT, double reward, double beta) {
        Random random = new Random();
        List<Node> nodes = new ArrayList<>();
        List<Node> delegates;
        Optional<DoubleSummaryStatistics> tempSummaryStatistics;
        DoubleSummaryStatistics DPOSPTTimeOverallStats = new DoubleSummaryStatistics();
        List<Double> DPOSPTTokenOverallStats = new ArrayList<>();
        List<Double> timeList = new ArrayList<>();
        List<Double> DPOSPTOverallStatsList = new ArrayList<>(),failRateList = new ArrayList<>();
        List<Node> lastDelegates = new ArrayList<>();          //记录上次见证人
        boolean isFirst=false;
        int failCount=0;

        for (int i = 0; i < correctNodeCount; ++i) {
            EarthPosition position = EarthPosition.randomPosition(random);
            nodes.add(new DPoSPTNode(position, initialTimeout, credit, token));
        }
        for (int i = 0; i < failedNodeCout; ++i) {
            EarthPosition position = EarthPosition.randomPosition(random);
            nodes.add(new FailedNode(position,credit,token));
        }
        Collections.shuffle(nodes, random);
        double broadcastTime=0;
        for(int r = 1;r <= round;r++){                                            //模拟n轮投票
            HashMap<Node,Double> voteTimeMap = new HashMap<>();
            for(Node node:nodes){
                voteTimeMap.put(node,random.nextGaussian());                       //正态模拟投票时间
            }
            HashMap<Node, List<Node>> votesMap = getVotes(nodes,"DPoSPT");//获取每个节点对应的投票者
            delegates = getDelegatesByVotes(votesMap, delegateNum*2);      //选出共识见证人和备选见证人

            List<Node> selectedDelegates = delegates.subList(0, delegates.size() / 2);  //选出共识见证人节点
            //System.out.println("共识见证人节点：");
            for (Node delegate : selectedDelegates) {
                //System.out.println(delegate.getPosition());
            }

            failRateList.add(Double.valueOf(selectedDelegates.stream().filter(n->n instanceof FailedNode).count())/selectedDelegates.size());
            List<Node> backupDelegates = delegates.subList(delegates.size() / 2,delegates.size()); //选出备用节点

            if(lastDelegates.isEmpty()){
                lastDelegates = new ArrayList<>(selectedDelegates);
                isFirst = true;
            }else if(isFirst){
                List<Node> finalLastDelegates = lastDelegates;
                double lastFailNodes = finalLastDelegates.stream().filter(n->n instanceof FailedNode).count();
                double rechooseRate = lastFailNodes == 0?0.0:delegates.stream().filter(n->n instanceof FailedNode && finalLastDelegates.contains(n)).count()/lastFailNodes;
                //        DPOSPTOverallStatsList.add(rechooseRate);
                isFirst=false;
            }
            PriorityQueue<Node> backupDelegatesQueue = new PriorityQueue<Node>(new Comparator<Node>() {
                @Override
                public int compare(Node o1, Node o2) {
                    return (int)Math.signum(o2.getCredit() - o1.getCredit());
                }
            });                                                                        //构建一个最大堆存备选节点
            backupDelegatesQueue.addAll(backupDelegates);
            Collections.shuffle(delegates, random);                                 //随机打乱

            Network network = new FullyConnectedNetwork(selectedDelegates, random);
            for (int i = 0; i < delegateNum; i++) {                                  //这里每个共识见证人出块一次
//      delegates = getNodesByCredit(nodes, delegateNum);
                network.setCreatorId(i);
                Simulation simulation = new Simulation(network);
//      for(Node node:nodes){
//        if(node instanceof DPoSPTNode){DPoSPTNode node1 =(DPoSPTNode)node;System.out.println(node1.getLastBlock());}
//      }
                if (!simulation.run(TIME_LIMIT)) {                                  //运行共识
                    //System.out.println("out of time");
                    continue;
                }
                List<DPoSPTNode> correctNodes = selectedDelegates.stream()
                        .filter(n -> n instanceof DPoSPTNode).map(node -> (DPoSPTNode) node)
                        .collect(Collectors.toList());
                Proposal output = correctNodes.get(0).getOutput();
                if (output != null && !correctNodes.stream().allMatch(Node::hasTerminated)) {
                    //System.out.println("WARNING: Not all DPOSPT nodes terminated.");
                    break;
                }

                Node creator = network.getCreator();
                if (output != null) {
                    double maxTime = voteTimeMap.entrySet().stream().mapToDouble(entry->entry.getValue()).max().getAsDouble();
                    double minTime = voteTimeMap.entrySet().stream().mapToDouble(entry->entry.getValue()).min().getAsDouble();
                    double totalToken = votesMap.get(creator).stream().mapToDouble(node->(1.0 + 9/(maxTime -minTime)*(maxTime - voteTimeMap.get(node)))*node.getCredit()*node.getToken()).sum();
                    votesMap.get(creator).stream().filter(node->node!=creator).forEach(node -> node.setToken(node.getToken() +  (1.0 + 9/(maxTime -minTime)*(maxTime - voteTimeMap.get(node)))*beta*reward*node.getToken()*node.getCredit()/totalToken));  //更新投票节点奖励
                    creator.setToken(creator.getToken() + (1.0-beta)*reward);   //更新生产节点奖励
                    //System.out.println("生产节点："+creator.toString());
                    creator.setCredit(creator.getCredit() <= 0.5 ? creator.getCredit() + 0.01 : 0.5 - 0.5 * Math.cos(Math.PI * creator.getCredit()));    //更新信任度
                    //System.out.println("更新生产节点的奖励: "+creator.getToken());
                    //System.out.println("更新信任度："+creator.getCredit());
                    nodes.stream().filter(node -> !correctNodes.contains(node)).forEach(node -> node.addBlock(output.getBlock()));//更新其他节点账本
                    broadcastTime = nodes.stream().mapToDouble(node->network.getLatency(node,creator)).max().getAsDouble();
                    Block block=output.getBlock();
                    /*
                    System.out.println("block-Index: "+block.getIndex());
                    System.out.println("block-PreviousHash: "+block.getPreviousHash());
                    System.out.println("block-Timestamp: "+block.getTimestamp());
                    System.out.println("block-Transactions: "+block.getTransactions().toString());

                     */


                }else{
//          System.out.println("作恶");//节点作恶，更换节点
                    //System.out.println("作恶节点："+creator.toString());
                    creator.setCredit(creator.getCredit()/10.0);                          //更新信任度
                    network.getNodes().set(i % nodes.size(),backupDelegatesQueue.poll());  //取信任度最大的节点替代
                    backupDelegatesQueue.add(creator);
                    failCount++;
                }                                                                                              //
                correctNodes.stream().forEach(n -> n.resetPhase());                       //重置共识状态


                //System.out.println("Algorand times: " + correctNodes.stream().mapToDouble(Node::getTerminationTime).sorted().boxed().collect(Collectors.toList()));
                double finalBroadcastTime = broadcastTime;
//        tempSummaryStatistics = Optional.of(nodes.stream().filter(node -> node.getTerminationTime()>0)
//                .mapToDouble(node->node.getTerminationTime())
//                .summaryStatistics());
                OptionalDouble maxTime= nodes.stream().filter(node -> node.getTerminationTime()>0)
                        .mapToDouble(node->node.getTerminationTime()+ finalBroadcastTime)
                        .max();
                if(maxTime.isPresent()) timeList.add(maxTime.getAsDouble());
                //前五个区块确认时延
//        if(r%10==0&&i<6){
//          System.out.println(nodes.stream().filter(node -> node.getTerminationTime()>0)
//                  .mapToDouble(node->1000*(node.getTerminationTime()+ finalBroadcastTime)).max());
//        }

//        tempSummaryStatistics.ifPresent(DPOSPTTimeOverallStats::combine);           //统计出块时间
//        nodes.stream()
//                .forEach(node -> System.out.println(node.getTerminationTime()));
//        for (Node node : selectedDelegates) {
//          if (node instanceof DPoSPTNode) {
//            node.setCredit(node.getCredit() <= 0.5 ? node.getCredit() + 0.01 : 0.5 - 0.5 * Math.cos(Math.PI * node.getCredit()));    //更新信任度
//            node.setToken(node.getToken() + 1);
//            node.restart();
//            System.out.println(node.getToken());
//          } else {
//            node.setCredit(node.getCredit() / 10.0);
//          }
//        }
                selectedDelegates.stream().forEach(node ->node.restart());
            }
        }
//    nodes.stream().forEach(node -> System.out.println(node instanceof DPoSPTNode?"好节点"+node.getCredit():"坏节点"+node.getCredit()));
        DPOSPTTokenOverallStats = nodes.stream().filter(node -> node.getToken()>=0)
                .map(Node::getToken)
                .collect(Collectors.toList());
        double gini = gini(DPOSPTTokenOverallStats); //获取基尼系数
//    double PBFTAvgTime = DPOSPTTimeOverallStats.getAverage();  //获取平均区块确认时延
        double avgTime = timeList.stream().mapToDouble(n->n).average().getAsDouble();
        DPOSPTOverallStatsList.add(failRateList.stream().mapToDouble(n->n).average().getAsDouble());
//    System.out.println(failRateList.toString());
        DPOSPTOverallStatsList.add(gini);
        DPOSPTOverallStatsList.add(avgTime);
//    DPOSPTOverallStatsList.add(PBFTAvgTime);
//    nodes.stream().filter(node -> node instanceof FailedNode).forEach(x->System.out.println(x.getCredit()+":"+x.getToken()));
//    System.out.println(nodes.stream().filter(node -> node instanceof DPoSPTNode)
//            .mapToDouble(node -> ((DPoSPTNode) node).getBlockChain().get(((DPoSPTNode) node).getBlockChain().size()-1).getIndex())
//            .max().getAsDouble());
//    System.out.println(failCount);
        return DPOSPTOverallStatsList;
    }

    private static Map<Integer,Map<String,Object>> runDPOSPTGetInfo(
            double initialTimeout, int correctNodeCount, int failedNodeCout, double token, double credit, int round, int delegateNum, double TIME_LIMIT, double reward, double beta) {
        // 存储区块信息
        Map<Integer,Map<String,Object>> resultInfo = new HashMap<>();// 1~round 表示第i轮投票信息，0表示节点，-1表示统计数据


        Random random = new Random();
        List<Node> nodes = new ArrayList<>();
        List<Node> delegates;
        Optional<DoubleSummaryStatistics> tempSummaryStatistics;
        DoubleSummaryStatistics DPOSPTTimeOverallStats = new DoubleSummaryStatistics();
        List<Double> DPOSPTTokenOverallStats = new ArrayList<>();
        List<Double> timeList = new ArrayList<>();
        List<Double> DPOSPTOverallStatsList = new ArrayList<>(),failRateList = new ArrayList<>();
        List<Node> lastDelegates = new ArrayList<>();          //记录上次见证人
        boolean isFirst=false;
        int failCount=0;

        for (int i = 0; i < correctNodeCount; ++i) {
            EarthPosition position = EarthPosition.randomPosition(random);
            nodes.add(new DPoSPTNode(position, initialTimeout, credit, token));
        }
        for (int i = 0; i < failedNodeCout; ++i) {
            EarthPosition position = EarthPosition.randomPosition(random);
            nodes.add(new FailedNode(position,credit,token));
        }
        Collections.shuffle(nodes, random);
        double broadcastTime=0;
        // 存储节点
        Map<String,Object> nodeInfo = new HashMap<>();
        nodeInfo.put("nodeInfo",nodes);
        resultInfo.put(0,nodeInfo);
        for(int r = 1;r <= round;r++){                                            //模拟n轮投票
            // 存储第i轮投票的数据
            Map<String,Object> voteInfo = new HashMap<>();

            HashMap<Node,Double> voteTimeMap = new HashMap<>();
            for(Node node:nodes){
                voteTimeMap.put(node,random.nextGaussian());                       //正态模拟投票时间
            }
            HashMap<Node, List<Node>> votesMap = getVotes(nodes,"DPoSPT");//获取每个节点对应的投票者
            delegates = getDelegatesByVotes(votesMap, delegateNum*2);      //选出共识见证人和备选见证人

            List<Node> selectedDelegates = delegates.subList(0, delegates.size() / 2);  //选出共识见证人节点
            //System.out.println("共识见证人节点：");
            //for (Node delegate : selectedDelegates) {
            //    System.out.println(delegate.getPosition());
            //}
            // 存储共识见证人节点
            voteInfo.put("selectedDelegates",selectedDelegates);

            failRateList.add(Double.valueOf(selectedDelegates.stream().filter(n->n instanceof FailedNode).count())/selectedDelegates.size());
            List<Node> backupDelegates = delegates.subList(delegates.size() / 2,delegates.size()); //选出备用节点

            if(lastDelegates.isEmpty()){
                lastDelegates = new ArrayList<>(selectedDelegates);
                isFirst = true;
            }else if(isFirst){
                List<Node> finalLastDelegates = lastDelegates;
                double lastFailNodes = finalLastDelegates.stream().filter(n->n instanceof FailedNode).count();
                double rechooseRate = lastFailNodes == 0?0.0:delegates.stream().filter(n->n instanceof FailedNode && finalLastDelegates.contains(n)).count()/lastFailNodes;
                //        DPOSPTOverallStatsList.add(rechooseRate);
                isFirst=false;
            }
            PriorityQueue<Node> backupDelegatesQueue = new PriorityQueue<Node>(new Comparator<Node>() {
                @Override
                public int compare(Node o1, Node o2) {
                    return (int)Math.signum(o2.getCredit() - o1.getCredit());
                }
            });                                                                        //构建一个最大堆存备选节点
            backupDelegatesQueue.addAll(backupDelegates);
            Collections.shuffle(delegates, random);                                 //随机打乱

            Network network = new FullyConnectedNetwork(selectedDelegates, random);
            List<Map<String,Object>> vlist=new ArrayList<>();
            for (int i = 0; i < delegateNum; i++) {//这里每个共识见证人出块一次
                Map<String,Object> map1=new HashMap<>();
//      delegates = getNodesByCredit(nodes, delegateNum);
                network.setCreatorId(i);
                Simulation simulation = new Simulation(network);
//      for(Node node:nodes){
//        if(node instanceof DPoSPTNode){DPoSPTNode node1 =(DPoSPTNode)node;System.out.println(node1.getLastBlock());}
//      }
                if (!simulation.run(TIME_LIMIT)) {                                  //运行共识
                    //System.out.println("out of time");
                    continue;
                }
                List<DPoSPTNode> correctNodes = selectedDelegates.stream()
                        .filter(n -> n instanceof DPoSPTNode).map(node -> (DPoSPTNode) node)
                        .collect(Collectors.toList());
                Proposal output = correctNodes.get(0).getOutput();
                if (output != null && !correctNodes.stream().allMatch(Node::hasTerminated)) {
                    //System.out.println("WARNING: Not all DPOSPT nodes terminated.");
                    break;
                }

                Node creator = network.getCreator();
                if (output != null) {
                    double maxTime = voteTimeMap.entrySet().stream().mapToDouble(entry->entry.getValue()).max().getAsDouble();
                    double minTime = voteTimeMap.entrySet().stream().mapToDouble(entry->entry.getValue()).min().getAsDouble();
                    double totalToken = votesMap.get(creator).stream().mapToDouble(node->(1.0 + 9/(maxTime -minTime)*(maxTime - voteTimeMap.get(node)))*node.getCredit()*node.getToken()).sum();
                    votesMap.get(creator).stream().filter(node->node!=creator).forEach(node -> node.setToken(node.getToken() +  (1.0 + 9/(maxTime -minTime)*(maxTime - voteTimeMap.get(node)))*beta*reward*node.getToken()*node.getCredit()/totalToken));  //更新投票节点奖励
                    creator.setToken(creator.getToken() + (1.0-beta)*reward);   //更新生产节点奖励
                    //System.out.println("生产节点："+creator.toString());
                    creator.setCredit(creator.getCredit() <= 0.5 ? creator.getCredit() + 0.01 : 0.5 - 0.5 * Math.cos(Math.PI * creator.getCredit()));    //更新信任度
                    //System.out.println("更新生产节点的奖励: "+creator.getToken());
                    //System.out.println("更新信任度："+creator.getCredit());
                    nodes.stream().filter(node -> !correctNodes.contains(node)).forEach(node -> node.addBlock(output.getBlock()));//更新其他节点账本
                    broadcastTime = nodes.stream().mapToDouble(node->network.getLatency(node,creator)).max().getAsDouble();
                    Block block=output.getBlock();
                    /*
                    System.out.println("block-Index: "+block.getIndex());
                    System.out.println("block-PreviousHash: "+block.getPreviousHash());
                    System.out.println("block-Timestamp: "+block.getTimestamp());
                    System.out.println("block-Transactions: "+block.getTransactions().toString());

                     */
                    map1.put("生产节点",creator);
                    map1.put("区块",block);
                    map1.put("作恶节点",null);
                }else{
//          System.out.println("作恶");//节点作恶，更换节点
                    //System.out.println("作恶节点："+creator.toString());
                    creator.setCredit(creator.getCredit()/10.0);                          //更新信任度
                    network.getNodes().set(i % nodes.size(),backupDelegatesQueue.poll());  //取信任度最大的节点替代
                    backupDelegatesQueue.add(creator);
                    failCount++;
                    map1.put("作恶节点",creator);
                    map1.put("生产节点",null);
                    map1.put("区块",null);
                }
                //
                vlist.add(map1);
                correctNodes.stream().forEach(n -> n.resetPhase());                       //重置共识状态
                //System.out.println();

                //System.out.println("Algorand times: " + correctNodes.stream().mapToDouble(Node::getTerminationTime).sorted().boxed().collect(Collectors.toList()));
                double finalBroadcastTime = broadcastTime;
//        tempSummaryStatistics = Optional.of(nodes.stream().filter(node -> node.getTerminationTime()>0)
//                .mapToDouble(node->node.getTerminationTime())
//                .summaryStatistics());
                OptionalDouble maxTime= nodes.stream().filter(node -> node.getTerminationTime()>0)
                        .mapToDouble(node->node.getTerminationTime()+ finalBroadcastTime)
                        .max();
                if(maxTime.isPresent()) timeList.add(maxTime.getAsDouble());
                //前五个区块确认时延
//        if(r%10==0&&i<6){
//          System.out.println(nodes.stream().filter(node -> node.getTerminationTime()>0)
//                  .mapToDouble(node->1000*(node.getTerminationTime()+ finalBroadcastTime)).max());
//        }

//        tempSummaryStatistics.ifPresent(DPOSPTTimeOverallStats::combine);           //统计出块时间
//        nodes.stream()
//                .forEach(node -> System.out.println(node.getTerminationTime()));
//        for (Node node : selectedDelegates) {
//          if (node instanceof DPoSPTNode) {
//            node.setCredit(node.getCredit() <= 0.5 ? node.getCredit() + 0.01 : 0.5 - 0.5 * Math.cos(Math.PI * node.getCredit()));    //更新信任度
//            node.setToken(node.getToken() + 1);
//            node.restart();
//            System.out.println(node.getToken());
//          } else {
//            node.setCredit(node.getCredit() / 10.0);
//          }
//        }
                selectedDelegates.stream().forEach(node ->node.restart());
            }
            voteInfo.put("blockInfo",vlist);
            resultInfo.put(r,voteInfo);
        }

//    nodes.stream().forEach(node -> System.out.println(node instanceof DPoSPTNode?"好节点"+node.getCredit():"坏节点"+node.getCredit()));
        DPOSPTTokenOverallStats = nodes.stream().filter(node -> node.getToken()>=0)
                .map(Node::getToken)
                .collect(Collectors.toList());
        double gini = gini(DPOSPTTokenOverallStats); //获取基尼系数
//    double PBFTAvgTime = DPOSPTTimeOverallStats.getAverage();  //获取平均区块确认时延
        double avgTime = timeList.stream().mapToDouble(n->n).average().getAsDouble();
        DPOSPTOverallStatsList.add(failRateList.stream().mapToDouble(n->n).average().getAsDouble());
//    System.out.println(failRateList.toString());
        DPOSPTOverallStatsList.add(gini);
        DPOSPTOverallStatsList.add(avgTime);
//    DPOSPTOverallStatsList.add(PBFTAvgTime);
//    nodes.stream().filter(node -> node instanceof FailedNode).forEach(x->System.out.println(x.getCredit()+":"+x.getToken()));
//    System.out.println(nodes.stream().filter(node -> node instanceof DPoSPTNode)
//            .mapToDouble(node -> ((DPoSPTNode) node).getBlockChain().get(((DPoSPTNode) node).getBlockChain().size()-1).getIndex())
//            .max().getAsDouble());
//    System.out.println(failCount);
        Map<String,Object> statsListInfo = new HashMap<>();
        nodeInfo.put("statsListInfo",DPOSPTOverallStatsList);
        resultInfo.put(-1,statsListInfo);

        return resultInfo;
    }

    public static double gini(List<Double> values) {
        double sumOfDifference = values.stream()
                .flatMapToDouble(v1 -> values.stream().mapToDouble(v2 -> Math.abs(v1 - v2))).sum();
        double mean = values.stream().mapToDouble(v -> v).average().getAsDouble();
        return sumOfDifference / (2 * values.size() * values.size() * mean);
    }

    private static String statisticsToCompactString(DoubleSummaryStatistics statistics) {
        return String.format("min=%.2f, max=%.2f, average=%.2f",
                statistics.getMin(), statistics.getMax(), statistics.getAverage());
    }

    private static HashMap<Node,List<Node>> getVotes(List<Node> nodes, String type){
        HashMap<Node,List<Node>> result = new HashMap<>();
        Random random = new Random(1234);
        for(Node node:nodes){
            double max = 0;int randint = random.nextInt(nodes.size()),maxId = randint,num = type.equals("DPoS")? nodes.size() : nodes.size()/2;
            for(int i = randint;i<randint +num;i++){           //DPoSPT随机选出一半投票
                double feature = type.equals("DPoS")?Math.pow(random.nextDouble(),1/nodes.get(i%nodes.size()).getToken()):Math.pow(random.nextDouble(),1/(nodes.get(i%nodes.size()).getCredit()*nodes.get(i%nodes.size()).getToken()));
                if( feature> max){
                    max = feature;
                    maxId = i%nodes.size();
                }
            }
            if(result.containsKey(nodes.get(maxId))){
                result.get(nodes.get(maxId)).add(node);
            }else {
                //result.put(nodes.get(maxId),new ArrayList<>(){{add(node);}});
                List<Node> list = new ArrayList<>();
                list.add(node);
                result.put(nodes.get(maxId),list);
            }
        }
        for(Node node:nodes){
            Set<Node> nodeSet = result.keySet();
            if(!nodeSet.contains(node)){
                result.put(node,new ArrayList<>());
            }
        }
        return result;
    }


    private static List<Node> getDelegatesByVotes(HashMap<Node,List<Node>> votesMap, int delegateNum){
        HashMap<Node,Double> votes = new HashMap<>();
        for(Map.Entry<Node,List<Node>> entry:votesMap.entrySet()){
            votes.put(entry.getKey(),entry.getValue().stream().mapToDouble(node -> node.getCredit()* node.getToken()).sum());
        }
        List<Map.Entry<Node,Double>> list = new ArrayList<Map.Entry<Node,Double>>(votes.entrySet());
        List<Node> result = new ArrayList<>();
        Collections.sort(list, new Comparator<Map.Entry<Node, Double>>() {
            @Override
            public int compare(Map.Entry<Node, Double> o1, Map.Entry<Node, Double> o2) {
                return o2.getValue().compareTo(o1.getValue());
            }
        });
        for( Map.Entry<Node,Double> e :list.subList(0,delegateNum)){
            result.add(e.getKey());
        }
        return result;
    }


    private  static  List<Node> getNodesByCredit(List<Node> nodes, int n){
        List<Node> result = new ArrayList<>();
        Random random = new Random(1234);
        HashMap<Node,Double> nodeCreditMap = new HashMap<>();
        PriorityQueue<Node> minHeap = new PriorityQueue<Node>(new Comparator<Node>(){
            @Override
            public int compare(Node n1, Node n2){
                return  (int) Math.signum(nodeCreditMap.get(n1) - nodeCreditMap.get(n2));
            }
        });
        for(Node node:nodes){
            nodeCreditMap.put(node,Math.pow(random.nextDouble(),1/node.getCredit()));
            if(minHeap.size() < n) {
                minHeap.add(node);
            }else{
                double value = Math.pow(random.nextDouble(),1/node.getCredit());
                if(nodeCreditMap.get(minHeap.peek()) < value){
                    minHeap.poll();
                    minHeap.add(node);
                }
            }
        }
        for(Node node:minHeap){
            result.add(node);
        }
        return result;
    }

    public static double getAvgFailRate(int n,String type, double initialTimeout, int correctNodeCount, int failedNodeCout, double token, double credit, int round, int delegateNum, double TIME_LIMIT,  double reward,double beta){
        double sum=0;
        for(int i=0;i<n;i++){
            if(type.equals("DPoS")){
                List<Double> DPOSOverallStats = runDPOS(initialTimeout, correctNodeCount, failedNodeCout, token, credit, round, delegateNum, reward);
                sum+=DPOSOverallStats.get(0);
            }else{
                List<Double> DPOSPTOverallStats = runDPOSPT(initialTimeout, correctNodeCount, failedNodeCout, token, credit, round, delegateNum,TIME_LIMIT, reward, beta);
                sum+=DPOSPTOverallStats.get(0);
            }
        }
        return sum/n;
    }

    public static double getAvgGini(int n,String type, double initialTimeout, int correctNodeCount, int failedNodeCout, double token, double credit, int round, int delegateNum, double TIME_LIMIT, double reward, double beta){
        double sum=0;
        for(int i=0;i<n;i++){
            if(type.equals("DPoS")){
                List<Double> DPOSOverallStats = runDPOS(initialTimeout, correctNodeCount, failedNodeCout, token, credit, round, delegateNum,reward);
                sum+=DPOSOverallStats.get(1);
            }else{
                List<Double> DPOSPTOverallStats = runDPOSPT(initialTimeout, correctNodeCount, failedNodeCout, token, credit, round, delegateNum,TIME_LIMIT, reward, beta);
                sum+=DPOSPTOverallStats.get(1);
            }
        }
        return sum/n;
    }

    public static double getAvgTime(int n,double initialTimeout, int correctNodeCount, int failedNodeCout, double token, double credit, int round, int delegateNum, double TIME_LIMIT, double reward, double beta){
        double sum=0;
        for(int i=0;i<n;i++){
            List<Double> DPOSPTOverallStats = runDPOSPT(initialTimeout, correctNodeCount, failedNodeCout, token, credit, round, delegateNum,TIME_LIMIT, reward, beta);
            sum+=DPOSPTOverallStats.get(2);
        }
        return sum/n;
    }
}

